/*
 * Copyright (c) 2007, 2008 Johannes Krampf <Johannes.Krampf@htw-dresden.de>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "bstring/bstrlib.h"
#include "language.h"
#include "database.h"
#include "general.h"

struct languages *langs = NULL;

struct lookup {
	char index[8];
	struct language* key;
};

struct languages {
	int numlanguages;
	struct lookup* lang;
};

struct language* NewLanguage() {
	struct language * lang;

	if (NULL == langs) {
		langs = malloc(sizeof(struct languages));
		if (NULL == langs) {
			MemError();
			return NULL;
		}
		langs->numlanguages = 0;
		langs->lang = malloc(sizeof(struct lookup));
		if (NULL == langs->lang) {
			MemError();
			free(langs);
			langs = NULL;
			return NULL;
		}
	}

	lang = malloc(sizeof(struct language));
	if (NULL == lang) {
		MemError();
		return NULL;
	}
	lang->numuniques = -1;
	lang->numtenses = -1;
	lang->numcombines = -1;
	lang->numpronouns = -1;
	lang->numverbs = -1;
	lang->numsuffixes = -1;
	lang->numrules = -1;
	lang->uniques = NULL;
	lang->tenses = NULL;
	lang->combines = NULL;
	lang->verbs = NULL;
	lang->suffixes = NULL;
	lang->rules = NULL;

	return lang;
}

int DeleteLanguage(struct language* lang) {
/* Pointer müssen bei der Initialisierung auf NULL gesetzt werden,
 * da es sonst zu invaliden Operationen kommen kann. */
	int i, j;
	char buffer[128];
	struct lookup* temp;

	if (NULL == lang) {
		fprintf(stderr, "DeleteLanguage: Nullzeiger uebergeben.\n");
		return -1;
	}

	for (i=0;i<lang->numrules;i++) {
		buffer[0] = '\0';
		strcpy(buffer, lang->name);
		strcat(buffer, "-");
		strcat(buffer, lang->rules[i]);
		DeleteMemFileByName(buffer);
	}

	buffer[0] = '\0';
	strcpy(buffer, lang->name);
	strcat(buffer, "_suffixes");
	DeleteMemFileByName(buffer);
	buffer[0] = '\0';
	strcpy(buffer, lang->name);
	strcat(buffer, "_verbs");
	DeleteMemFileByName(buffer);

	free(lang->rules);
	free(lang->verbs);
	free(lang->suffixes);
	free(lang->uniques);
	free(lang->tenses);
	free(lang->combines);
	free(lang);

	langs->numlanguages--;
	if (0 != langs->numlanguages) {
		temp = malloc(langs->numlanguages * sizeof(struct lookup));
		if (NULL == temp) {
			MemError();
			return -1;
		}
	}else {
		temp = NULL;
	}

	
	j = 0;
	for(i=0;i<langs->numlanguages;i++) {
		if (langs->lang[i].key != lang) {
			j++;
			strcpy(temp[j].index, langs->lang[i].index);
			temp[j].key = langs->lang[i].key;
		}
	}
	
	free(langs->lang);
	langs->lang = temp;
	
	return 0;
}

int ClearLanguages() {
	int i;

	if (NULL != langs) {
		for(i=0;i<langs->numlanguages;i++) {
			DeleteLanguage(langs->lang[i].key);
		}
		free(langs);
		langs = NULL;
	}
	
	
	return 0;
}

struct language* LoadFile(char* filename) {
	int i, j, k, line, tempint, error = 0;
	struct bstrList *lines = NULL;
	bstring file = NULL, temp = NULL;
	bstring tempname = NULL;
	struct language * newlang;
	char buffer[8];
	
	int linenumbers[4] = { -1, -1, -1, -1 };
	struct tagbstring shash = bsStatic("#");
	struct tagbstring sdollar = bsStatic("$");
	struct tagbstring sections[4] = {bsStatic("#name"),
									bsStatic("#rules"),
									bsStatic("#suffixes"),
									bsStatic("#verbs") };
	struct tagbstring elements[4] = {bsStatic("$unique"),
									bsStatic("$tense"),
									bsStatic("$combine"),
									bsStatic("$pronouns") };

enum sect { NAME=0, RULES, SUFFIXES, VERBS };
enum elem { UNIQUE=0, TENSE, COMBINE, PRONOUNS };

	file = GetFileContent(filename);
	
	lines = bsplit (file, '\n');
	if (NULL == lines) {
		MemError();
		bdestroy (file);
		return NULL;
	}
	bdestroy (file);
	
	for(i=0;i<lines->qty;i++) {
		if (0 == bstrncmp(&shash, lines->entry[i], shash.slen)) {
			for(j=0;j<4;j++) {
				if (0 == bstrncmp(&sections[j], lines->entry[i], sections[j].slen)) {
					if (-1 != linenumbers[j]) {
						fprintf(stderr, "LoadFile: inkorrekte Dateistruktur fuer %s (mehrfache Sektion)\n", filename);
						bstrListDestroy (lines);
						return NULL;
					}
					linenumbers[j] = i;
				}
			}
		}
	}
	
	for(j=0;j<4;j++) {
		if (-1 == linenumbers[j]) {
			fprintf(stderr, "LoadFile: inkorrekte Dateistruktur fuer %s (fehlende Sektion)\n", filename);
			bstrListDestroy (lines);
			return NULL;
		}
	}	

	newlang = NewLanguage();
	if (NULL == newlang) {
		MemError();
		bstrListDestroy(lines);
		return NULL;
	}
	
	
/* Sektion #name */
	line = linenumbers[NAME];
	tempint = bstrchr(lines->entry[line], ' ');
	/* name */
	temp = bmidstr(lines->entry[line], tempint+1, lines->entry[line]->slen-tempint);
	strncpy(newlang->name, (char*)temp->data, MinLen(31, temp->slen));
	newlang->name[MinLen(31, temp->slen)] = '\0';
	bdestroy(temp);
	
	i = line;
	while ((++i < lines->qty) && (0 != bstrncmp(&shash, lines->entry[i], shash.slen))) {
		if (0 == bstrncmp(&sdollar, lines->entry[i], sdollar.slen)) {
			for(j=0;j<4;j++) {
				if (0 == bstrncmp(&elements[j], lines->entry[i], elements[j].slen)) {
					tempint = atoi((char *)(lines->entry[i]->data+elements[j].slen));
					switch(j) {
						case UNIQUE:
							if (-1 != newlang->numuniques) {
								fprintf(stderr, "LoadFile: %s: Element 'unique' mehrfach definiert.", filename);
								error = 1;
							}
							newlang->numuniques = tempint;
							newlang->uniques = malloc(newlang->numuniques * sizeof(char[8]));
							if (NULL == newlang->uniques) {
								MemError();
								DeleteLanguage(newlang);
								bstrListDestroy(lines);
								return NULL;
							}
							for(k=0;k<newlang->numuniques;k++) {
								i++;
								if (( (k+i) >= lines->qty ) ||
									(0 == lines->entry[i]->slen) ||
									(0 == bstrncmp(&sdollar, lines->entry[i], sdollar.slen)) ||
									(0 == bstrncmp(&shash, lines->entry[i], shash.slen))
								) {
									fprintf(stderr, "LoadFile: %s: %d Elemente fuer 'unique' gefunden, %d definiert.\n", filename, k, newlang->numuniques);
									DeleteLanguage(newlang);
									bstrListDestroy(lines);
									return NULL;
								}
								strncpy(newlang->uniques[k], (char*)lines->entry[i]->data, MinLen(4, lines->entry[i]->slen));
								newlang->uniques[k][MinLen(4, lines->entry[i]->slen)] = '\0';
							}
							break;
						case TENSE:
							if (-1 != newlang->numtenses) {
								fprintf(stderr, "Loadfile: %s: Element 'tense' mehrfach definiert.\n", filename);
								error = 1;
							}
							newlang->numtenses = tempint;
							newlang->tenses = malloc(newlang->numtenses * sizeof(char[8]));
							if (NULL == newlang->tenses) {
								MemError();
								DeleteLanguage(newlang);
								bstrListDestroy(lines);
								return NULL;
							}	
							for(k=0;k<newlang->numtenses;k++) {
								i++;
								if (( (k+i) >= lines->qty ) ||
									(0 == lines->entry[i]->slen) ||
									(0 == bstrncmp(&sdollar, lines->entry[i], sdollar.slen)) ||
									(0 == bstrncmp(&shash, lines->entry[i], shash.slen))
								) {
									fprintf(stderr, "LoadFile: %s: %d Elemente fuer 'tense' gefunden, %d definiert.\n", filename, k, newlang->numtenses);
									DeleteLanguage(newlang);
									bstrListDestroy(lines);
									return NULL;
								}
								strncpy(newlang->tenses[k], (char*)lines->entry[i]->data, MinLen(4, lines->entry[i]->slen));
								newlang->tenses[k][MinLen(4, lines->entry[i]->slen)] = '\0';
							}
							break;
						case COMBINE:
							if (-1 != newlang->numcombines) {
								fprintf(stderr, "LoadFile: %s: Element 'combine' mehrfach definiert.\n", filename);
								error = 1;
							}
							newlang->numcombines = tempint;
							newlang->combines = malloc(newlang->numcombines * sizeof(char[8]));
							if (NULL == newlang->combines) {
								MemError();
								DeleteLanguage(newlang);
								bstrListDestroy(lines);
								return NULL;
							}
							for(k=0;k<newlang->numcombines;k++) {
								i++;
								if (( (k+i) >= lines->qty ) ||
									(0 == lines->entry[i]->slen) ||
									(0 == bstrncmp(&sdollar, lines->entry[i], sdollar.slen)) ||
									(0 == bstrncmp(&shash, lines->entry[i], shash.slen))
								) {
									fprintf(stderr, "LoadFile: %s: %d Elemente fuer 'combine' gefunden, %d definiert.\n", filename, k, newlang->numcombines);
									DeleteLanguage(newlang);
									bstrListDestroy(lines);
									return NULL;
								}
								strncpy(newlang->combines[k], (char*)lines->entry[i]->data, MinLen(4, lines->entry[i]->slen));
								newlang->combines[k][MinLen(4, lines->entry[i]->slen)] = '\0';
							}
							break;
						case PRONOUNS:
							if (-1 != newlang->numpronouns) {
								MemError();
								error = 1;
							}
							newlang->numpronouns = tempint;
							break;				
					}
					if (1 == error) {
						bstrListDestroy(lines);
						DeleteLanguage(newlang);
						return NULL;
					}
				}
			}
		}
	}
	
	if (((0 >= newlang->numuniques) &&
		 (0 >= newlang->numtenses) &&
		 (0 >= newlang->numcombines)) ||
		(0 >= newlang->numpronouns)
		) {
		fprintf(stderr, "LoadFile: %s: Keine Elemente oder Pronomen definiert.\n", filename);
		bstrListDestroy (lines);
		DeleteLanguage(newlang);
		return NULL;
	}
	
/* Sektion #suffixes */
	line = linenumbers[SUFFIXES];

	i = line;
	k = 0;
	temp = cstr2bstr("");
	while ((++i < lines->qty) && (0 != bstrncmp(&shash, lines->entry[i], shash.slen))) {
		if (0 != lines->entry[i]->slen) {
			k++;
			if (0 != temp->slen) {
				bconchar(temp, '|');
			}
			bconcat(temp, lines->entry[i]);
		}
	}	
	newlang->numsuffixes = k;

	tempname = cstr2bstr(newlang->name);
	bcatcstr(tempname, "_suffixes");

	newlang->suffixes = malloc(newlang->numsuffixes * sizeof(char[16]));
	if ( (NULL == newlang->suffixes) || (0 != AddDatabaseEntry((char *)tempname->data, temp)) ) {
		MemError();
		DeleteLanguage(newlang);
		bstrListDestroy(lines);
		return NULL;
	}
	bdestroy(tempname);
	tempname = NULL;
	temp = NULL;
	
	for (k=0;k<newlang->numsuffixes;k++) {
		tempint = bstrchr(lines->entry[line+k+1], ' ');
		if (BSTR_ERR == tempint) {
			fprintf(stderr, "LoadFile: Keine Regeln fuer Suffix %s definiert.\n", (char *)lines->entry[line+k+1]->data);
			tempint = lines->entry[line+k+1]->slen;
		}
		strncpy(newlang->suffixes[k], (char *)lines->entry[line+k+1]->data, MinLen(tempint, 15));
		newlang->suffixes[k][MinLen(tempint, 15)] = '\0';
	}
	
/* Sektion #verbs */
	line = linenumbers[VERBS];

	i = line;
	k = 0;
	temp = cstr2bstr("");
	while ((++i < lines->qty) && (0 != bstrncmp(&shash, lines->entry[i], shash.slen))) {
		if (0 != lines->entry[i]->slen) {
			k++;
			if (0 != temp->slen) {
				bconchar(temp, '|');
			}
			bconcat(temp, lines->entry[i]);
		}
	}
	newlang->numverbs = k;

	tempname = cstr2bstr(newlang->name);
	bcatcstr(tempname, "_verbs");

	newlang->verbs = malloc(newlang->numverbs * sizeof(char[32]));
	if ( (NULL == newlang->verbs) || (0 != AddDatabaseEntry((char *)tempname->data, temp)) ) {
		MemError();
		DeleteLanguage(newlang);
		bstrListDestroy(lines);
		bdestroy(tempname);
		return NULL;
	}
	bdestroy(tempname);
	tempname = NULL;
	temp = NULL;

	for (k=0;k<newlang->numverbs;k++) {
		tempint = bstrchr(lines->entry[line+k+1], ' ');
		if (BSTR_ERR == tempint) {
			fprintf(stderr, "LoadFile: Keine Regeln fuer Verb %s definiert.\n", (char *)lines->entry[line+k+1]->data);
			tempint = lines->entry[line+k+1]->slen;
		}
		strncpy(newlang->verbs[k], (char *)lines->entry[line+k+1]->data, MinLen(tempint, 15));
		newlang->verbs[k][MinLen(tempint, 15)] = '\0';
	}

/* Sektion #rules */
	line = linenumbers[RULES];

	k = 0;
	i = line;
	while ((++i < lines->qty) && (0 != bstrncmp(&shash, lines->entry[i], shash.slen))) {
		if (0 == bstrncmp(&sdollar, lines->entry[i], sdollar.slen)) {
			k++;
			tempname = cstr2bstr(newlang->name);
			bconchar(tempname, '-');
			bcatcstr(tempname, (char *)lines->entry[i]->data+1);
			temp = cstr2bstr("");
			j = i;
			while ((++j < lines->qty) && (0 != bstrncmp(&shash, lines->entry[j], shash.slen)) && (0 != bstrncmp(&sdollar, lines->entry[j], sdollar.slen))) {
				if (0 != temp->slen) {
					bconchar(temp, '|');
				}
				bconcat(temp, lines->entry[j]);
				i++;
			}
			if (0 != AddDatabaseEntry((char *)tempname->data, temp)) {
				MemError();
				DeleteLanguage(newlang);
				bstrListDestroy(lines);
				return NULL;
			}
			bdestroy(tempname);
			tempname = NULL;
		}
	}

	newlang->numrules = k;
	
	if (0 == newlang->numrules) {
		fprintf(stderr, "LoadFile: %s: Keine Regeln festgelegt.\n", filename);
		DeleteLanguage(newlang);
		bstrListDestroy(lines);
		return NULL;
	}	
	
	newlang->rules = malloc(newlang->numrules * sizeof(char[32]));
	if (NULL == newlang->rules) {
		MemError();
		DeleteLanguage(newlang);
		bstrListDestroy(lines);
		return NULL;
	}

	k = 0;
	i = line;
	while ((++i < lines->qty) && (0 != bstrncmp(&shash, lines->entry[i], shash.slen))) {
		if (0 == bstrncmp(&sdollar, lines->entry[i], sdollar.slen)) {
			strncpy(newlang->rules[k], (char *)lines->entry[i]->data+1, MinLen(lines->entry[i]->slen, 31));
			newlang->rules[k++][MinLen(lines->entry[i]->slen, 31)] = '\0';
		}
	}	
	
	bstrListDestroy (lines);
	
	langs->numlanguages++;
	langs->lang = realloc(langs->lang, langs->numlanguages * sizeof(struct lookup));
	if (NULL == langs->lang) {
		MemError();
		return NULL;
	}

	langs->lang[langs->numlanguages-1].key = newlang;	
	
	strncpy(buffer, filename, MinLen(7, strlen(filename)));
	buffer[MinLen(7, strlen(filename))] = '\0';
	
	tempint = strchr(buffer, '.')-buffer;
	
	if (0 < tempint) {
		buffer[tempint] = '\0';
	}
	
	strcpy(langs->lang[langs->numlanguages-1].index, buffer);
		
	return newlang;	
}

struct language* GetLanguage(char* code) {
	char buffer[128];
	int i;

	strncpy(buffer, code, MinLen(123, strlen(code)));
	buffer[MinLen(123, strlen(code))] = '\0';

	if (NULL != langs) {
		for(i=0;i<langs->numlanguages;i++) {
			if (0 == strcmp(langs->lang[i].index, buffer)) {
				return langs->lang[i].key;
			}
		}
	}
	strcat(buffer, ".lang");
	return LoadFile(buffer);
}
